package io.adrainty.gbatis.core;

import io.adrainty.gbatis.datasource.DataSourceType;
import io.adrainty.gbatis.datasource.UnPooledDataSource;
import io.adrainty.gbatis.entity.MapperStatement;
import io.adrainty.gbatis.transation.JdbcTransaction;
import io.adrainty.gbatis.transation.Transaction;
import io.adrainty.gbatis.transation.TransactionType;
import lombok.NoArgsConstructor;
import lombok.SneakyThrows;
import org.dom4j.Attribute;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.Node;
import org.dom4j.io.SAXReader;

import javax.sql.DataSource;
import java.io.InputStream;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 解析mybatis-config.xml
 * 创建SqlSessionFactory对象
 * @author adrainty
 * @version v1.0.0
 * @since 2024/1/14 22:14
 */

@NoArgsConstructor
public class SqlSessionFactoryBuilder {

    private final SAXReader reader = new SAXReader();

    /**
     * 创建SQLSessionFactory
     * @param is 输入流mybatis-config.xml
     * @return SQLSessionFactory对象
     */
    @SneakyThrows
    public SqlSessionFactory build(InputStream is) {
        Document document = reader.read(is);
        Transaction transaction = resolveTransaction(document);
        Map<String, MapperStatement> mapperStatements = resolveMapperStatement(document);
        return new SqlSessionFactory(transaction, mapperStatements);
    }

    @SneakyThrows
    private Map<String, MapperStatement> resolveMapperStatement(Document rootDocument) {
        Map<String, MapperStatement> mapperStatementMap = new HashMap<>();
        String mapperXPath = "//mapper";
        List<Node> mapperNodes = rootDocument.selectNodes(mapperXPath);
        for (Node m : mapperNodes) {
            Element e = (Element) m;
            String resource = e.attribute("resource").getValue();
            InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream(resource);
            Document document = reader.read(is);
            Element mappers = document.getRootElement();
            String namespace = mappers.attribute("namespace").getValue();
            for (Element mapper : mappers.elements()) {
                String id = String.format("%s.%s", namespace, mapper.attribute("id").getValue());
                String sql = mapper.getText().trim();
                Attribute type = mapper.attribute("resultType");
                String resultType = type == null? "null": type.getValue();
                mapperStatementMap.put(id, new MapperStatement(sql, resultType));
            }
        }
        return mapperStatementMap;
    }

    @SneakyThrows
    private Transaction resolveTransaction(Document document) {
        String environmentXPath = "/configuration/environments";
        Element environments = (Element) document.selectSingleNode(environmentXPath);
        String defaultEnvironment = environments.attribute("default").getValue();
        String activeEnvironmentXPath = String.format("%s/environment[@id='%s']", environmentXPath, defaultEnvironment);
        Element environment = (Element) document.selectSingleNode(activeEnvironmentXPath);
        String type = environment.element("transactionManager").attribute("type").getValue();
        Element dataSourceElement = environment.element("dataSource");
        DataSource dataSource = resolveDataSource(dataSourceElement);
        return switch (TransactionType.getByCode(type)) {
            case JDBC -> new JdbcTransaction(dataSource);
        };
    }

    private DataSource resolveDataSource(Element dataSourceElement) {
        String type = dataSourceElement.attribute("type").getValue();
        Map<String, String> dataSourcePattern = new HashMap<>();
        for (Element element : dataSourceElement.elements()) {
            dataSourcePattern.put(element.attribute("name").getValue(), element.attribute("value").getValue());
        }
        return switch (DataSourceType.getByCode(type)) {
            case UNPOOLED -> new UnPooledDataSource(dataSourcePattern.get("driver"),
                    dataSourcePattern.get("url"),
                    dataSourcePattern.get("username"),
                    dataSourcePattern.get("password")
            );
        };
    }

}
